package com.dearbinge.utils;

import java.io.*;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.*;

/**
 * 
 *
 *
 */
public abstract class Lang {

	/**
	 * 
	 * @return
	 */
	public static RuntimeException impossiable() {
		return new RuntimeException("r u kidding me!");
	}

	/**
	 * 
	 * @param message
	 * @return
	 */
	public static RuntimeException oneThrow(String message) {
		return new RuntimeException(message);
	}

	/**
	 * 
	 * @param e
	 * @return
	 */
	public static RuntimeException uncheck(Throwable e) {
		if (e instanceof RuntimeException) {
			return (RuntimeException) e;
		}

		if (e instanceof InvocationTargetException) {
			Throwable cause = e.getCause();
			return new RuntimeException(cause);
		}

		return new RuntimeException(e);
	}

	/**
	 * 
	 * @param e
	 * @return
	 */
	public static Throwable unwrap(Throwable e) {
		Throwable unwrapp = e;

		while (true) {
			if (unwrapp instanceof InvocationTargetException) {
				unwrapp = ((InvocationTargetException) unwrapp).getTargetException();
			} else if (unwrapp instanceof UndeclaredThrowableException) {
				unwrapp = ((UndeclaredThrowableException) unwrapp).getUndeclaredThrowable();
			} else {
				return unwrapp;
			}
		}
	}

	/**
	 * 
	 * @param throwables
	 * @return
	 */
	public static ComboException comboThrow(Throwable... throwables) {
		Collection<Throwable> throwablesAsCollection = Arrays.asList(throwables);
		return comboThrow(throwablesAsCollection);
	}

	/**
	 * 
	 * @param throwables
	 * @return
	 */
	public static ComboException comboThrow(Collection<Throwable> throwables) {
		return new ComboException(throwables);
	}

	/**
	 * 
	 *
	 *
	 */
	@SuppressWarnings("serial")
	static final class ComboException extends RuntimeException {
		private Collection<Throwable> throwables;

		public ComboException() {
			throwables = new LinkedList<Throwable>();
		}

		public ComboException(Collection<Throwable> throwables) {
			this.throwables = throwables;
		}

		public ComboException push(Throwable throwable) {
			this.throwables.add(throwable);
			return this;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.lang.Throwable#getMessage()
		 */
		@Override
		public String getMessage() {
			StringBuilder sb = new StringBuilder();
			for (Throwable throwable : this.throwables) {
				sb.append(throwable.getMessage()).append("\n");
			}
			return sb.toString();
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.lang.Throwable#getLocalizedMessage()
		 */
		@Override
		public String getLocalizedMessage() {
			StringBuilder sb = new StringBuilder();
			for (Throwable throwable : this.throwables) {
				sb.append(throwable.getLocalizedMessage()).append("\n");
			}
			return sb.toString();
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.lang.Throwable#getCause()
		 */
		@Override
		public Throwable getCause() {
			if (this.throwables.isEmpty()) {
				return null;
			}

			Iterator<Throwable> it = this.throwables.iterator();
			return it.next();
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.lang.Throwable#printStackTrace()
		 */
		@Override
		public void printStackTrace() {
			for (Throwable throwable : this.throwables) {
				throwable.printStackTrace();
			}
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.lang.Throwable#printStackTrace(java.io.PrintStream)
		 */
		@Override
		public void printStackTrace(PrintStream s) {
			for (Throwable throwable : this.throwables) {
				throwable.printStackTrace(s);
			}
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.lang.Throwable#printStackTrace(java.io.PrintWriter)
		 */
		@Override
		public void printStackTrace(PrintWriter s) {
			for (Throwable throwable : this.throwables) {
				throwable.printStackTrace(s);
			}
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.lang.Throwable#getStackTrace()
		 */
		@Override
		public StackTraceElement[] getStackTrace() {
			List<StackTraceElement> result = new LinkedList<StackTraceElement>();
			for (Throwable throwable : this.throwables) {
				result.addAll(Arrays.asList(throwable.getStackTrace()));
			}

			return result.toArray(new StackTraceElement[] {});
		}

	}

	/**
	 * 
	 * @param <T>
	 * @param obj
	 * @param out
	 */
	public static <T extends Serializable> void serialize(T obj, OutputStream out) {
		ObjectOutputStream oos = null;

		try {

			oos = new ObjectOutputStream(out);
			oos.writeObject(obj);
			oos.flush();
		} catch (Exception e) {
			throw uncheck(e);
		} finally {
			IOs.freeQuietly(oos, out);
		}
	}

	/**
	 * 
	 * @param in
	 * @return
	 */
	public static Object deserialize(InputStream in) {
		ObjectInputStream ooi = null;

		try {

			ooi = new ObjectInputStream(in);
			return ooi.readObject();
		} catch (Exception e) {
			throw uncheck(e);
		} finally {
			IOs.freeQuietly(ooi, in);
		}
	}

	/**
	 * 
	 * @param actual
	 * @param safe
	 * @return
	 */
	public static <T> T nullSafe(T actual, T safe) {
		return null == actual ? safe : actual;
	}

	/**
	 * 
	 * @param type
	 * @param length
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public static <T> T[] newArray(Class<T> type, int length) {
		return (T[]) Array.newInstance(type, length);
	}

	/**
	 * 
	 * @param reference
	 * @param length
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public static <T> T[] newArray(T[] reference, int length) {
		Class<?> type = reference.getClass().getComponentType();
		return (T[]) Array.newInstance(type, length);
	}

	/**
	 * 
	 * @param first
	 * @param second
	 * @param type
	 * @return
	 */
	public static <T> T[] concat(T[] first, T[] second, Class<T> type) {
		T[] result = newArray(type, first.length + second.length);
		System.arraycopy(first, 0, result, 0, first.length);
		System.arraycopy(second, 0, result, first.length, second.length);
		return result;
	}

	/**
	 * 
	 * @param element
	 * @param array
	 * @return
	 */
	public static <T> T[] concat(T element, T[] array) {
		T[] result = newArray(array, array.length + 1);
		result[0] = element;
		System.arraycopy(array, 0, result, 1, array.length);
		return result;
	}

	/**
	 * 
	 * @param args
	 * @return
	 */
	public static Class<?>[] evalToTypes(Object... args) {
		Class<?>[] types = new Class[args.length];
		int i = 0;
		for (Object arg : args) {
			types[i++] = null == arg ? Object.class : arg.getClass();
		}

		return types;
	}

	/**
	 * 
	 * @param milliseconds
	 */
	public static void sleepQuietly(long milliseconds) {
		try {

			if (milliseconds > 0) {
				Thread.sleep(milliseconds);
			}
		} catch (Exception ingore) {
		}
	}

	/**
	 * 
	 * @return
	 */
	public static Stopwatch newStopwatch() {
		return new Stopwatch();
	}

	/**
	 * 
	 *
	 *
	 */
	static class Stopwatch {
		private long start;
		private long end;

		public long start() {
			start = System.currentTimeMillis();
			return start;
		}

		public long stop() {
			end = System.currentTimeMillis();
			return end;
		}

		public long getElapse() {
			return end - start;
		}
	}

	private Lang() {
	}
}
